package me.helllp.bootman.common.utils;

import java.util.concurrent.TimeUnit;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * 基于Redis的分布式锁的实现
 * 
 * @author Administrator
 *
 */
public class RedisLockUtil {
	/**
	 * redis操作对象
	 */
	private final RedisTemplate<String, Object> redisTemplate;
	
    /** 
     * 重试休眠时间 
     */  
    private static final int DEFAULT_SLEEP_MILLIS = 100;
    
    /** 
     * 锁的后缀 
     */  
    private static final String LOCK_SUFFIX = "_redis_lock";
    
    public RedisLockUtil(RedisTemplate<String, Object> redisTemplate) {
    	this.redisTemplate = redisTemplate;
	}
    
    /**
     * 进行加锁尝试
     * 
     * @param lockKey		锁的Key，系统会自动添加后缀
     * @param waitTimeOut	尝试的时间限制
     * @param keyExpire		锁的失效时间设置
     * 
     * @return	锁的失效时间，用来进行解锁操作
     */
    public Long tryLock(String lockKey, long waitTimeOut, long keyExpire){
    	String _key = lockKey +  LOCK_SUFFIX;
    	byte[] keyBytes = redisTemplate.getStringSerializer().serialize(_key);
    	
    	long start = currtTimeForRedis();

		//	循环尝试获取锁
    	while(currtTimeForRedis() - start < waitTimeOut){
    		//	锁的回收时间
    		long lockTimeOut = currtTimeForRedis() + keyExpire + 1;
    		
        	boolean hasLock = redisTemplate.execute(new RedisCallback<Boolean>() {

    			@Override
    			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {

    				byte[] value = redisTemplate.getStringSerializer().serialize("" + lockTimeOut);
    				
    				return connection.setNX(keyBytes, value);
    			}
    		});
        	
        	if(hasLock){
        		//	设置失效时间
                redisTemplate.expire(_key, lockTimeOut, TimeUnit.MILLISECONDS);
                return lockTimeOut;
        	}else{
        		Object result = redisTemplate.opsForValue().get(_key);
        		Long currt_lock_timeout = result==null ? null : (Long)result; 
        		
        		//	判断锁是否已经失效了
        		if (currt_lock_timeout != null && currt_lock_timeout < System.currentTimeMillis()){  
                     //	获取上一个锁到期时间，并设置现在的锁到期时间  
                     Long old_lock_timeout = (Long)redisTemplate.opsForValue().getAndSet(_key, lockTimeOut);
                     
                     //	表示当前线程设置锁成功！
                     if (old_lock_timeout != null && old_lock_timeout.equals(currt_lock_timeout)){  
                         //设置超时间，释放内存  
                         redisTemplate.expire(lockKey, lockTimeOut, TimeUnit.MILLISECONDS);  
   
                         //返回加锁时间  
                         return lockTimeOut;  
                     }  
                 }
        	}
        	
        	if(waitTimeOut > DEFAULT_SLEEP_MILLIS){
                try {
                	//	等待重试
                    TimeUnit.MILLISECONDS.sleep(DEFAULT_SLEEP_MILLIS);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }        		
        	}else{
        		break;
        	}
  
    	}
    	
    	return null;
    }
    
    public void unlock(String lockKey, long lockValue) {
    	String _key = lockKey + LOCK_SUFFIX;
    	
        //	获取redis中设置的时间  
        Object result = redisTemplate.opsForValue().get(_key);
        
        Long currt_lock_timeout = result ==null ? null : (Long)result;  
  
        //	如果是加锁者，则删除锁， 如果不是，则等待自动过期，重新竞争加锁  
        if (currt_lock_timeout != null && currt_lock_timeout == lockValue){  
            redisTemplate.delete(lockKey);  
        }  
    } 
    
    /**
     * 使用redis时间，避免多服务器时间的不一致
     * 
     * @return
     */
    private long currtTimeForRedis(){  
        return redisTemplate.execute(new RedisCallback<Long>() {  
            @Override  
            public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {  
                return redisConnection.time();  
            }  
        });  
    }  
}
